//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<FileOp.cpp>
///	@path	~/src/lib/system/
///	@date	2007/11/22
///	@desc	Os's file Op encapsulation.

#include "config/config.h"

#include "lib/system/file_op.h"
#include "lib/system/thread.h"

namespace xeres
{
	// FileExist
	FILE_STATUS FileOp::FileExist( const WString& path )
	{
		DWORD attr = ::GetFileAttributes( path.c_str() );
		if( attr == 0xffffffff )
		{
			return FILE_NOFILE;
		}
		if( attr & FILE_ATTRIBUTE_DIRECTORY )
        {
            return FILE_DIR;
        }
		return FILE_READY;
	}

	// FileOp
	FileOp::FileOp( void )
		: m_hFile( NULL )
		, m_status( 0 )
	{
	}

	// ~FileOp
	FileOp::~FileOp( void )
	{
		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
		{
			// another call is still using file op
			TRACE_ERROR( _S("FileOp::DTOR: Closing working file op.") );
		}

		if( m_hFile )
		{
			::CloseHandle( m_hFile );
			m_hFile = NULL;
		}
	}

	namespace
	{
		class _ReleaseStatus
		{
		public:
			// ctor
			_ReleaseStatus( volatile int * status )
				: m_status( status )
			{
			}

			// dtor
			~_ReleaseStatus( void )
			{
				if( m_status )
					atom_xchg( m_status , 0 );
			}

			//
			void set( volatile int * status )
			{
				m_status = status;
			}

		private:
			// status
			volatile int *	m_status;
		};
	}

	// OpenFile
	FILE_STATUS FileOp::OpenFile( const WString& path , bool shared /*= false */ )
	{
		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
			// another call is still using file op
			return FILE_OPENED;

		// setup release status protect
		_ReleaseStatus s_status( &m_status );

		if( m_hFile )
		{
			return FILE_OPENED;
		}

		// test if file exist
		switch( FileExist( path ) )
		{
		case FILE_NOFILE:
			TRACE_ERROR( _S("FileOp::CreateFile: File '%s' does not exist.") , path.c_str() );
			return FILE_NOFILE;
		case FILE_DIR:
			TRACE_ERROR( _S("FileOp::CreateFile: Path '%s' is a directory.") , path.c_str() );
			return FILE_DIR;
		}

		DWORD share_flag = shared ? FILE_SHARE_READ : 0;
		// try to open file
		HANDLE hFile = ::CreateFile(
			path.c_str() , GENERIC_READ , share_flag , NULL , OPEN_EXISTING , 0 , NULL );

		// test if failed
		if( hFile == NULL )
		{
			__win32_error( _S("FileOp::OpenFile: Cannot open file :%s.") );
			return FILE_ERROR;
		}

		m_hFile = (void*)hFile;

		wchar_t filename[OS_MAX_PATH];
		::GetFullPathName( path.c_str() , OS_MAX_PATH , filename , NULL );
		m_path = filename;

		return FILE_READY;
	}

	// CreateFile
	FILE_STATUS FileOp::CreateFile( const WString& path , bool create_always , bool append )
	{
		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
			// another call is still using file op
			return FILE_OPENED;

		// setup release status protect
		_ReleaseStatus s_status( &m_status );

		if( m_hFile )
		{
			return FILE_OPENED;
		}

		// test if file exist
		int exist = FileExist( path ) ;

		if( exist == FILE_DIR )
		{
			TRACE_ERROR( _S("FileOp::CreateFile: Path '%s' is a directory.") , path.c_str() );
			return FILE_DIR;
		}

		if( !create_always && !append && exist == FILE_READY )
		{
			TRACE_ERROR( _S("FileOp::CreateFile: Cannot create file '%s' cause file exists.") , path.c_str() );
			return FILE_EXISTED;
		}

		DWORD open_flag = 0;
		if( exist == FILE_READY )
			open_flag = append ? OPEN_EXISTING : TRUNCATE_EXISTING;
		else
			open_flag = CREATE_NEW;

		if( create_always )
			open_flag |= CREATE_ALWAYS;

		// try to open file
		HANDLE hFile = ::CreateFile(
			path.c_str() , GENERIC_READ | GENERIC_WRITE , 0 , NULL , open_flag , 0 , NULL );

		// test if failed
		if( hFile == NULL || hFile == INVALID_HANDLE_VALUE )
		{
			__win32_error( _S("FileOp::CreateFile: Cannot open file :%s.") );
			return FILE_ERROR;
		}

		m_hFile = (void*)hFile;
		
		wchar_t filename[OS_MAX_PATH];
		::GetFullPathName( path.c_str() , OS_MAX_PATH , filename , NULL );
		m_path = filename;

		return FILE_READY;
	}

	// OpenDir
	FILE_STATUS FileOp::OpenDir( const WString& path )
	{
		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
			// another call is still using file op
			return FILE_OPENED;

		// setup release status protect
		_ReleaseStatus s_status( &m_status );

		if( m_hFile )
		{
			return FILE_OPENED;
		}

		// test if file exist
		switch( FileExist( path ) )
		{
		case FILE_NOFILE:
			TRACE_ERROR( _S("FileOp::OpenDir: Directory '%s' does not exist.") , path.c_str() );
			return FILE_NOFILE;
		case FILE_READY:
			TRACE_ERROR( _S("FileOp::CreateFile: Path '%s' is not a directory.") , path.c_str() );
			return FILE_EXISTED;
		}

		// try to open directory
		HANDLE hFile = ::CreateFile( path.c_str() ,
			GENERIC_READ ,
			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE ,
			NULL ,
			OPEN_EXISTING ,
			FILE_FLAG_BACKUP_SEMANTICS ,
			NULL );

		// test if failed
		if( hFile == NULL )
		{
			__win32_error( _S("FileOp::OpenDir: Cannot open directory :%s.") );
			return FILE_ERROR;
		}

		m_hFile = (void*)hFile;
		
		wchar_t filename[OS_MAX_PATH];
		::GetFullPathName( path.c_str() , OS_MAX_PATH , filename , NULL );
		m_path = filename;

		return FILE_READY;
	}

	// Close
	FILE_STATUS FileOp::Close( void )
	{
		if( m_hFile == NULL )
		{
			return FILE_NOFILE;
		}

		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
			// another call is still using file op
			return FILE_WORKING;

		m_path.clear();
		::CloseHandle( m_hFile );
		m_hFile = NULL;

		// release working status
		atom_xchg( &m_status , 0 );

		return FILE_READY;
	}

	// Status
	FILE_STATUS FileOp::Status( void ) const
	{
		if( m_hFile == NULL )
			return FILE_NOFILE;

		BY_HANDLE_FILE_INFORMATION info;
		if( ::GetFileInformationByHandle( (HANDLE)m_hFile , &info ) != TRUE )
		{
			__win32_error( _S("FileOp::Status: Failed to get file info: %s.") );
			return FILE_ERROR;
		}
		if( info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
        {
            return FILE_DIR;
        }
		return m_status == 1 ? FILE_WORKING : FILE_READY;
	}

	// Size
	uint64 FileOp::Size( void )
	{
		assert( m_hFile );

		uint64 size = 0;
		if( ::GetFileSizeEx( (HANDLE)m_hFile , (PLARGE_INTEGER)&size ) == FALSE )
		{
			__win32_error( _S("FileOp::Size: Failed to get file size: %s.") );
		}
		return size;
	}

	static inline void _timetToSystemTime( __time64_t t , LPSYSTEMTIME pst )
	{
		FILETIME ft;
		LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000LL;
		ft.dwLowDateTime = (DWORD) ll;
		ft.dwHighDateTime = (DWORD)(ll >> 32);
		::FileTimeToSystemTime( &ft, pst );
	}

	static inline void _systemTimeToTimet( SYSTEMTIME st , __time64_t * pt )
	{
		FILETIME ft;
		::SystemTimeToFileTime( &st, &ft );
		LONGLONG ll;
		ULARGE_INTEGER ui;
		ui.LowPart = ft.dwLowDateTime;
		ui.HighPart = ft.dwHighDateTime;
		ll = (((uint64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
		*pt = (DWORD)((LONGLONG)(ui.QuadPart - 116444736000000000LL) / 10000000LL);
	}

	static inline void _fileTimeToTimet( FILETIME& ft , __time64_t * pt )
	{
		LONGLONG ll;
		ULARGE_INTEGER ui;
		ui.LowPart = ft.dwLowDateTime;
		ui.HighPart = ft.dwHighDateTime;
		ll = (((uint64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
		*pt = (DWORD)((LONGLONG)(ui.QuadPart - 116444736000000000LL) / 10000000LL);
	}

	static inline void _filetime_to_datetime( FILETIME& ftime , DateTime& dt )
	{
		__time64_t time;
		_fileTimeToTimet( ftime , &time );
		dt = time;
	}

	// GetFileTime
	bool FileOp::GetFileTime( DateTime& creation , DateTime& accessed , DateTime& modification )
	{
		BY_HANDLE_FILE_INFORMATION fileinfo;
		if( ::GetFileInformationByHandle( (HANDLE)m_hFile , &fileinfo ) == FALSE )
		{
			__win32_error( _S("FileOp::GetFileTime: Failed to get file time: %s.") );
			return false;
		}
		_filetime_to_datetime( fileinfo.ftCreationTime , creation );
		_filetime_to_datetime( fileinfo.ftLastAccessTime , accessed );
		_filetime_to_datetime( fileinfo.ftLastWriteTime , modification );
		return true;
	}

	// Tell
	uint64 FileOp::Tell( void )
	{
		uint64 pos = 0;
		LARGE_INTEGER move;
		move.QuadPart = 0;
		if( ::SetFilePointerEx( (HANDLE)m_hFile , move , (PLARGE_INTEGER)&pos , FILE_CURRENT ) == FALSE )
		{
			__win32_error( _S("FileOp::Tell: Failed to get file pointer: %s.") );
		}
		return pos;
	}

	// Seek
	bool FileOp::Seek( int64 pos , bool abs_seek )
	{
		DWORD movement = abs_seek ? FILE_BEGIN : FILE_CURRENT;
		LARGE_INTEGER move;
		move.QuadPart = pos;
		if( ::SetFilePointerEx( (HANDLE)m_hFile , move , NULL , movement ) == FALSE )
		{
			__win32_error( _S("FileOp::Tell: Failed to set file pointer: %s.") );
			return false;
		}
		return true;
	}

	// SeekEof
	bool FileOp::SeekEof( void )
	{
		if( ::SetEndOfFile( m_hFile ) == FALSE )
		{
			__win32_error( _S("FileOp::SeekEof: Failed to set eof: %s.") );
			return false;
		}
		return true;
	}

	// Read
	FILE_STATUS FileOp::Read( void * buffer , size_t len , size_t * readsize )
	{
		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
			// another call is still using file op
			return FILE_WORKING;

		DWORD dwReadSize = 0;
		BOOL result = ::ReadFile( (HANDLE)m_hFile , buffer , (DWORD)len , &dwReadSize , NULL );

		if( readsize )
			*readsize = dwReadSize;

		// release working status
		atom_xchg( &m_status , 0 );

		if( result == FALSE )
		{
			if( ::GetLastError() == ERROR_HANDLE_EOF )
			{
				return FILE_EOF;
			}

			__win32_error( _S("FileOp::Read: Failed to read file: %s.") );
			return FILE_ERROR;
		}
		return FILE_READY;
	}

	// Write
	FILE_STATUS FileOp::Write( const void * buffer , size_t len , size_t * writesize )
	{
		// get working status
		if( atom_xchg( &m_status , 1 ) == 1 )
			// another call is still using file op
			return FILE_WORKING;

		DWORD dwWriteSize = 0;
		BOOL result = ::WriteFile( (HANDLE)m_hFile , buffer , (DWORD)len , &dwWriteSize , NULL );
		if( writesize )
			*writesize = dwWriteSize;

		// release working status
		atom_xchg( &m_status , 0 );

		if( result == FALSE )
		{
			if( ::GetLastError() == ERROR_HANDLE_EOF )
				return FILE_EOF;

			__win32_error( _S("FileOp::Write: Failed to write file: %s.") );
			return FILE_ERROR;
		}
		return FILE_READY;
	}

	FILE_STATUS FileOp::CopyFile( const WString& src , const WString& dst , bool overwrite )
	{
		// test if file exist
		switch( FileExist( src ) )
		{
		case FILE_NOFILE:
			TRACE_ERROR( _S("FileOp::CopyFile: File '%s' does not exist.") , src.c_str() );
			return FILE_NOFILE;
		case FILE_DIR:
			TRACE_ERROR( _S("FileOp::CopyFile: Path '%s' is a directory.") , src.c_str() );
			return FILE_DIR;
		}

		switch( FileExist( dst ) )
		{
		case FILE_DIR:
			TRACE_ERROR( _S("FileOp::CopyFile: Path '%s' is a directory.") , dst.c_str() );
			return FILE_DIR;
		case FILE_READY:
			if( !overwrite )
			{
				TRACE_ERROR( _S("FileOp::CopyFile: File '%s' exists.") , dst.c_str() );
				return FILE_NOFILE;
			}
			break;
		}

		BOOL result = ::CopyFile( src.c_str() , dst.c_str() , overwrite ? FALSE : TRUE );
		if( result == FALSE )
		{
			__win32_error( _S("FileOp::CopyFile: Failed to copy file : %s.") );
			return FILE_ERROR;
		}
		return FILE_READY;
	}

	FILE_STATUS FileOp::DeleteFile( const WString& path )
	{
		// test if file exist
		switch( FileExist( path ) )
		{
		case FILE_NOFILE:
			TRACE_ERROR( _S("FileOp::DeleteFile: File '%s' does not exist.") , path.c_str() );
			return FILE_NOFILE;
		case FILE_DIR:
			TRACE_ERROR( _S("FileOp::DeleteFile: Path '%s' is a directory.") , path.c_str() );
			return FILE_DIR;
		}

		BOOL result = ::DeleteFile( path.c_str() );
		if( result == FALSE )
		{
			__win32_error( _S("FileOp::CopyFile: Failed to delete file : %s.") );
			return FILE_ERROR;
		}
		return FILE_READY;
	}

	FILE_STATUS FileOp::CreateDir( const WString& path )
	{
		BOOL result = ::CreateDirectory( path.c_str() , NULL );
		if( result != TRUE )
		{
			__win32_error( _S("FileOp::CreateDir: Failed to create directory : %s.") );
			return FILE_ERROR;
		}
		return FILE_READY;
	}

	FILE_STATUS FileOp::ListDir( const WString& path , const WString& spec ,
		std::vector<WString>& files )
	{

		WIN32_FIND_DATA FindFileData;
		HANDLE hFind = ::FindFirstFile( (path + _S("/") + spec).c_str() , &FindFileData );

		if( hFind == INVALID_HANDLE_VALUE )
		{
			__win32_error( _S("FileOp::ListDir: Failed to list dir: %s.") );
			return FILE_ERROR;
		}

		do
		{
			// skip self
			if( wcscmp( FindFileData.cFileName , _S(".") ) == 0 )
				continue;

			if( wcscmp( FindFileData.cFileName , _S("..") ) == 0 )
				continue;

			// skip hidden file
			if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN )
				continue;

			files.push_back( FindFileData.cFileName );
		}while( ::FindNextFile( hFind , &FindFileData ) != 0 );

		DWORD err = ::GetLastError();
		::FindClose( hFind );

		if( err != ERROR_NO_MORE_FILES )
		{
			__win32_error( _S("FileOp::ListDir: Failed to list dir: %s.") );
			return FILE_ERROR;
		}

		return FILE_READY;
	}

	// EnumerateFiles
	FILE_STATUS FileOp::EnumerateFiles( const WString& path , const WString& spec ,
		function<void(const EnumFileData&)> handler )
	{
		WIN32_FIND_DATA FindFileData;
		HANDLE hFind = ::FindFirstFile( (path + _S("/") + spec).c_str() , &FindFileData );

		if( hFind == INVALID_HANDLE_VALUE )
		{
			__win32_error( _S("FileOp::ListDir: Failed to list dir: %s.") );
			return FILE_ERROR;
		}

		do
		{
			// skip self
			if( wcscmp( FindFileData.cFileName , _S(".") ) == 0 )
				continue;

			if( wcscmp( FindFileData.cFileName , _S("..") ) == 0 )
				continue;

			// skip hidden file
			if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN )
				continue;

			EnumFileData efd;
			efd.status = ( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ? FILE_DIR : FILE_READY;
			efd.size = FindFileData.nFileSizeLow;
			_filetime_to_datetime( FindFileData.ftCreationTime , efd.creation );
			_filetime_to_datetime( FindFileData.ftLastAccessTime , efd.accessed );
			_filetime_to_datetime( FindFileData.ftLastWriteTime , efd.modified );
			efd.name = FindFileData.cFileName;

			handler( efd );
			
		}while( ::FindNextFile( hFind , &FindFileData ) != 0 );

		DWORD err = ::GetLastError();
		::FindClose( hFind );

		if( err != ERROR_NO_MORE_FILES )
		{
			__win32_error( _S("FileOp::ListDir: Failed to list dir: %s.") );
			return FILE_ERROR;
		}

		return FILE_READY;
	}

} // namespace xeres
